package com.beidu.lottery.call.service.portal.component;


import com.beidu.lottery.call.constant.ActivityTypeEnum;
import com.beidu.lottery.call.constant.CacheKey;
import com.beidu.lottery.call.errorcode.CommonCode;
import com.beidu.lottery.call.entity.PortalActivityRule;
import com.beidu.lottery.call.entity.dto.ActivityLotteryDTO;
import com.beidu.lottery.call.exception.ServiceException;
import com.beidu.lottery.call.service.portal.ActivityLotteryPrizeService;
import com.beidu.lottery.call.service.portal.ActivityRuleService;
import com.beidu.lottery.call.service.portal.UserDataService;
import com.beidu.lottery.call.utils.StockRedisUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;

/**
 * 抽奖算法组件
 * <p>
 * 抽奖原理：
 * 假设我们有三个奖品，概率分别是20%、30%和50%。
 * 将0%到100%的区间划分为三个部分：0%-20%、20%-50%和50%-100%。
 * 然后生成一个随机百分比，看它落在哪个区间，就抽到哪个奖品。
 *
 * @author 北渡
 */
@Component
@RequiredArgsConstructor
public class LotteryComponent {
    private final UserDataService userDataService;
    private final ActivityRuleService activityRuleService;
    private final ActivityLotteryPrizeService lotteryPrizeService;
    private final StockRedisUtil stockRedisUtil;

    /**
     * 是否达到阈值
     *
     * @return true:是, false:否
     * @author 北渡
     */
    private boolean isReachedThreshold(PortalActivityRule rule) {
        // 剩余中奖名额 < 1
        return userDataService.queryLotteryRemainingQuota(rule) < 1;
    }

    /**
     * 减扣中奖名额
     *
     * @param id 活动ID
     * @return true:减扣成功, false:减扣失败
     * @author 北渡
     */
    private boolean decrWinQuota(Integer id) {
        PortalActivityRule rule = activityRuleService.getActivityRuleCache(id, ActivityTypeEnum.LOTTERY);
        // 没有阈值
        if (rule.getWinThreshold() == null) {
            return true;
        }
        // 已到阈值, 减扣失败
        if (this.isReachedThreshold(rule)) {
            return false;
        }
        // 减扣中奖名额
        return stockRedisUtil.decrLimit(CacheKey.genWinQuotaKey(id), 1);
    }

    /**
     * 减扣库存
     *
     * @param id1 活动ID
     * @param id2 关联ID（库存表）
     * @return true:减扣成功, false:减扣失败
     * @author 北渡
     */
    private boolean decrStock(Integer id1, Integer id2) {
        return stockRedisUtil.decrLimit(CacheKey.genInventoryKey(id1, id2), 1);
    }

    /**
     * 获取默认奖品
     *
     * @param prizes 奖品池
     * @return 默认奖品
     * @author 北渡
     */
    private ActivityLotteryDTO getDefaultPrize(List<ActivityLotteryDTO> prizes) {
        Optional<ActivityLotteryDTO> op = prizes.stream().filter(p -> p.getDefaultPrize() == 1).findFirst();
        return op.orElseThrow(() -> new ServiceException(CommonCode.LOTTERY_CONFIG_PRIZE_ERROR.getMsg()));
    }

    /**
     * 进行抽奖
     * <p>
     * 避免多线程竞争同一个Random实例，使用ThreadLocalRandom类为每个线程提供了自己的随机数生成器
     *
     * @param id 活动ID
     * @return 抽中的奖品
     * @author 北渡
     */
    public ActivityLotteryDTO draw(Integer id) {
        // 奖品池
        List<ActivityLotteryDTO> prizes = lotteryPrizeService.getPortalPrizesCache(id);
        // 生成一个0到100之间的随机数
        double rn = ThreadLocalRandom.current().nextDouble() * 100;
        // 累积概率
        double cumulativeProbability = 0.0;
        // 线性匹配
        for (ActivityLotteryDTO prize : prizes) {
            cumulativeProbability += prize.getProbability();
            // 如果随机数小于或等于当前的累积概率，那么就匹配到这个区间
            if (rn <= cumulativeProbability) {
                // 未中奖（未配置奖品）
                if (prize.getPrizeId() == null) {
                    prize.setWon(false);
                    return prize;
                }
                // 减扣中奖名额
                if (!this.decrWinQuota(id)) {
                    // 已到阈值，使用默认奖品（保底奖品）
                    return this.getDefaultPrize(prizes);
                }
                // 默认奖品（保底奖品）无库存
                if (prize.getStock() == null) {
                    return prize;
                }
                // 减扣奖品库存
                if (!this.decrStock(id, prize.getId())) {
                    // 库存不足, 使用默认奖品（保底奖品）
                    return this.getDefaultPrize(prizes);
                }
                return prize;
            }
        }
        // 如果所有奖品的概率加起来不等于1，那么可能抽不到奖品
        throw new ServiceException(CommonCode.LOTTERY_CONFIG_PROBABILITY_ERROR.getMsg());
    }

    /**
     * 本地调试
     *
     * @author 北渡
     */
    public static void main(String[] args) {
        // 定义概率数组，可以是任何非负数，只要总和是100
        double[] probabilities = {0.1, 19.9, 10, 10, 10, 20, 20, 10};
        // 线性匹配
        Supplier<Object> supplier = () -> {
            double rn = ThreadLocalRandom.current().nextDouble() * 100;
            System.out.println("随机数: " + rn);
            double totalProbability = 0;
            for (int j = 0; j < probabilities.length; j++) {
                totalProbability += probabilities[j];
                System.out.println("累积概率: " + totalProbability);
                if (rn <= totalProbability) {
                    System.out.println("rn: " + rn + ", index: " + (j + 1));
                    break;
                }
            }
            return null;
        };
        // 抽奖次数
        int times = 20;
        // 执行测试
        for (int i = 0; i < times; i++) {
            supplier.get();
        }
    }

}